package com.mimo.common.configuration.semaphore.aspect;

import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.CollectionUtils;

import com.mimo.common.configuration.mrlock.component.MultiRedisKeysGenerator;
import com.mimo.common.configuration.semaphore.annotation.DistributedRedisSemaphore;

@Aspect
public class DistributedRedisSemaphoreAspect implements Ordered {
  private static final Logger log = LoggerFactory.getLogger(DistributedRedisSemaphoreAspect.class);

  private final RedissonClient client;

  public DistributedRedisSemaphoreAspect(RedissonClient client) {
    this.client = client;
  }

  @Around(value = "@annotation(annotation)")
  public Object around(ProceedingJoinPoint joinPoint, DistributedRedisSemaphore annotation) throws Throwable {
    String key = extract(annotation.key(), joinPoint);
    log.debug("尝试 Semaphore 加锁 Key:{}", key);
    RSemaphore semaphore = client.getSemaphore(key);
    boolean isSet = semaphore.trySetPermits(annotation.maxPermits());
    if (!isSet) {
      log.warn("Semaphore key[{}] had been setted before", key);
    }
    boolean isAcquired = semaphore.tryAcquire(annotation.waited(), annotation.timeunit());
    if (isAcquired) {
      try {
        return joinPoint.proceed();
      } finally {
        semaphore.release();
        log.debug("Semaphore 释放 Key:{}", key);
      }
    } else {
      throw annotation.exception().newInstance();
    }
  }

  /**
   * 新增用于处理Key的SPEL解析
   * 
   * @param key
   * @param joinPoint
   * @return
   */
  private String extract(String key, ProceedingJoinPoint joinPoint) {
    String k = key;

    if (key.contains(ParserContext.TEMPLATE_EXPRESSION.getExpressionPrefix())) { // 如果是SPEL表达式,则需要做SPEL解析
      MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
      String[] parameters = methodSignature.getParameterNames();

      EvaluationContext context = new StandardEvaluationContext();
      for (int pIndex = 0; pIndex < parameters.length; pIndex++) {
        context.setVariable(parameters[pIndex], joinPoint.getArgs()[pIndex]);
      }
      ExpressionParser parser = new SpelExpressionParser();
      k = parser.parseExpression(key, new TemplateParserContext()).getValue(context, String.class);
    }

    return k;
  }

  /**
   * 主要完成从函数签名入参中，根据具体的 {@link MultiRedisKeysGenerator}找到其所需要的参数，交付到它，再由它去渲染出关键的多Key
   * 
   * @param args
   * @param generator
   * @return
   */
  @SuppressWarnings({ "unchecked", "rawtypes" })
  private Set<String> extract(Object[] args, boolean firstEnable, MultiRedisKeysGenerator generator) {
    final Class clz = getClass(generator.getClass().getGenericSuperclass(), 0);

    List<Object> targets = Arrays.asList(args).stream().filter(clz::isInstance).collect(Collectors.toList());

    if (targets.isEmpty() || (targets.size() > 1 && !firstEnable)) {
      throw new IllegalArgumentException("Failed to find or found out duplicate args with same type[" + clz + "]");
    }

    Set<String> data = generator.generate(targets.get(0));
    if (CollectionUtils.isEmpty(data)) {
      throw new IllegalArgumentException("Extract Map cannot be empty or null");
    }

    return data;
  }

  @SuppressWarnings("rawtypes")
  private Class getClass(Type type, int i) {
    if (type instanceof ParameterizedType) { // 处理泛型类型
      return getGenericClass((ParameterizedType) type, i);
    } else if (type instanceof TypeVariable) {
      return getClass(((TypeVariable) type).getBounds()[0], 0); // 处理泛型擦拭对象
    } else {// class本身也是type，强制转型
      return (Class) type;
    }
  }

  @SuppressWarnings("rawtypes")
  private Class getGenericClass(ParameterizedType parameterizedType, int i) {
    Object genericClass = parameterizedType.getActualTypeArguments()[i];
    if (genericClass instanceof ParameterizedType) { // 处理多级泛型
      return (Class) ((ParameterizedType) genericClass).getRawType();
    } else if (genericClass instanceof GenericArrayType) { // 处理数组泛型
      return (Class) ((GenericArrayType) genericClass).getGenericComponentType();
    } else if (genericClass instanceof TypeVariable) { // 处理泛型擦拭对象
      return getClass(((TypeVariable) genericClass).getBounds()[0], 0);
    } else {
      return (Class) genericClass;
    }
  }

  @Override
  public int getOrder() {
    return 0;
  }

}
